///////////////////////////////////////////////////////////////////////////////
//
// lighthouse3D - 3D Software Library
//
// File: C3DSFileHandler.cpp
//
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2003-2013 Marcus Reinhardt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
///////////////////////////////////////////////////////////////////////////////
//
// You can find a copy of the GNU General Public License (gpl.txt) in the
// directory of the library.
//
///////////////////////////////////////////////////////////////////////////////
#include "C3DSFileHandler.h"

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::C3DSFileHandler( ... )
///////////////////////////////////////////////////////////////////////////////
C3DSFileHandler::C3DSFileHandler()
{
  pMeshStorage = NULL;

  statsMeshCount  = 0;
  statsTotalVerts = 0;
  statsTotalFaces = 0;
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::~C3DSFileHandler( ... )
///////////////////////////////////////////////////////////////////////////////
C3DSFileHandler::~C3DSFileHandler()
{
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::load3DSFile( ... )
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::load3DSFile(const char *fname, CMeshStorage *storageHandler)
{
  bool retValue;

  texList.n = 0;

#ifndef disableDebug
  logFile = fopen("C3DSFileHandlerLog.txt", "wt");
#endif

  writeDebugText("C3DSFileHandler LogFile\n");
  writeDebugText("-----------------------\n\n");
  writeDebugText("C3DSFileHandler: Begin to open 3DS-File\n");

  if( storageHandler == NULL )
  {
     writeDebugText("C3DSFileHandler: Invalid CMeshStorage Object\n");
     
     return false;
  }

  FILE *f = fopen(fname, "rb");

  if( f == NULL )
  {
     writeDebugText("C3DSFileHandler: Could not open file\n");

     return false;
  }

  pMeshStorage = storageHandler;

  int chunkID = 0;
  int chunkLength = 0;

  retValue = loadChunkHeader(f, chunkID, chunkLength);

  if( retValue )
  {
     if(chunkID == CHUNK_MAIN)
     {
        if(!loadMainChunk(f, chunkLength))
        {
           writeDebugText("C3DSFileHandler: Could not read the MAIN CHUNK\n");
           retValue = false;
        }
     }
     else
     {
        writeDebugText("C3DSFileHandler: Could not find the MAIN CHUNK\n");
        retValue = false;
     }
  }

  fclose(f);

  writeDebugText("\nC3DSFileHandler: End\n");

#ifndef disableDebug
  fclose(logFile);
#endif

  pMeshStorage = NULL;

  return retValue;
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::loadChunkHeader( ... )
///////////////////////////////////////////////////////////////////////////////
// This function loads the header of a chunk consisting of ID and Length
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::loadChunkHeader(FILE *f, int &chunkID, int &chunkLength)
{
  writeDebugText("C3DSFileHandler: Loading chunk header\n");

  if( (f == NULL) || (feof(f)) )
  {
     return false;
  }

  return ( fread(&chunkID, 2, 1, f) && fread(&chunkLength, 4, 1, f) );
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::skipChunk( ... )
///////////////////////////////////////////////////////////////////////////////
// If the Chunk is not required you can skip it with this function
///////////////////////////////////////////////////////////////////////////////
void C3DSFileHandler::skipChunk(FILE *f, int chunkLength)
{
  writeDebugText("C3DSFileHandler: Skipping chunk\n");

  if( f != NULL )
  {          
     fseek(f, chunkLength - CHUNK_HEADER_SIZE, SEEK_CUR);
  }
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::loadMainChunk( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::loadMainChunk(FILE *f, int length)
{
  int usedChunkData = 0;
    
  writeDebugText("C3DSFileHandler: Begin to read the MAIN CHUNK\n");

  while( usedChunkData < length )
  {
     int chunkID = 0;
     int chunkLength = 0;
        
     if(!loadChunkHeader(f, chunkID, chunkLength))
     {
        writeDebugText("C3DSFileHandler: Error reading the MAIN CHUNK\n");
        break;
     }

     switch(chunkID)
     {
            ///////////////////////////////////////////////////////////////////
            // This chunk contains the editor data chunks;
            ///////////////////////////////////////////////////////////////////

            case CHUNK_EDIT:
            {
                writeDebugText("C3DSFileHandler: Found EDIT CHUNK\n");
                
                if(!loadEditorChunk(f, chunkLength - CHUNK_HEADER_SIZE))
                {
                    writeDebugText("C3DSFileHandler: Could not read EDIT CHUNK\n");
                    
                    return false;
                }

            } break;

            ///////////////////////////////////////////////////////////////////
            // If this chunk isn't needed, skip over it;
            ///////////////////////////////////////////////////////////////////

            default:
            {
                writeDebugText("C3DSFileHandler: Found UNKNOWN CHUNK => will be skipped\n");

                skipChunk(f, chunkLength);

            } break;
        }

        usedChunkData += chunkLength;
  } 

  writeDebugText("C3DSFileHandler: MAIN CHUNK read\n");

  return true;
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::loadEditorChunk( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::loadEditorChunk(FILE *f, int length)
{
  int usedChunkData = 0;
    
  writeDebugText("C3DSFileHandler: Begin to read an EDITOR CHUNK\n");

  while(usedChunkData < length)
  {
        int chunkID = 0;
        int chunkLength = 0;
        
        if(!loadChunkHeader(f, chunkID, chunkLength))
        {
            writeDebugText("C3DSFileHandler: Error reading an EDITOR CHUNK\n");

            return false;
        }

        switch(chunkID)
        {
            ///////////////////////////////////////////////////////////////////
            // This chunk contains information about an object in the 
            // scene.  If this chunk is in fact an object, its worth nothing
            // still that it doesn't mean its a triangle mesh.  Lights,
            // cameras, and more are considered objects as well.
            ///////////////////////////////////////////////////////////////////

            case CHUNK_EDIT_OBJECT:
            {
                ///////////////////////////////////////////////////////////////
                // Load in the name of the object;
                ///////////////////////////////////////////////////////////////

                writeDebugText("C3DSFileHandler: Found OBJECT CHUNK\n");

                char meshname[512] = {0};

                int t = 0;

                while(t < 512)
                {
                    meshname[t] = (char)getc(f);

                    if(meshname[t] == 0)
                    {
                        break;
                    }

                    t++;
                }

                writeDebugText("C3DSFileHandler: Object Name = ");
                writeDebugText(meshname);
                writeDebugText("\n");

                ///////////////////////////////////////////////////////////////
                // Process the object chunk;
                ///////////////////////////////////////////////////////////////

                if(!loadObjectChunk(f, chunkLength - CHUNK_HEADER_SIZE - t - 1, meshname))
                {
                    writeDebugText("C3DSFileHandler: Could not read OBJECT CHUNK\n");

                    return false;
                }
                
            } break;

            
            ///////////////////////////////////////////////////////////////
            // This chunk contains the name of the texture for the mesh
            ///////////////////////////////////////////////////////////////
            case CHUNK_EDIT_MATERIAL:
            {
            
                if(!loadMaterialChunk(f, chunkLength - CHUNK_HEADER_SIZE))
                {
                    writeDebugText("C3DSFileHandler: Could not read OBJECT CHUNK\n");

                    return false;
                }
                
            } break;    
                    
            ///////////////////////////////////////////////////////////////////
            // If this chunk isn't needed, skip over it;
            ///////////////////////////////////////////////////////////////////
            default:
            {
                writeDebugText("C3DSFileHandler: Found UNKNOWN CHUNK => will be skipped\n");

                skipChunk(f, chunkLength);

            } break;

        }

        usedChunkData += chunkLength;
  }

  writeDebugText("C3DSFileHandler: EDITOR CHUNK read\n");

  return (usedChunkData == length);
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::getTextureNr( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
int C3DSFileHandler::addTexture(char *matName)
{
  strcpy(texList.textures[texList.n].description, matName);
  
  if (texList.n+1 > maxTextures)
      return false;

  texList.n++;
  
  return (texList.n-1);
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::getTextureNr( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
int C3DSFileHandler::getTextureNr(char *matName)
{
  int i;
  
  for(i=0; i<texList.n; i++)
  {
     if (strcmp(texList.textures[i].description, matName) == 0)
     {
         return i;
     }
  }

  return 0;
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::loadMaterialChunk( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::loadMaterialChunk(FILE *f, int length)
{
  char fname[256];
  char matname[512];
  int usedChunkData = 0;
  // int subChunkID = 0;
  // int subChunkLength = 0;

  writeDebugText("C3DSFileHandler: Begin to read an MATERIAL CHUNK\n");

  while(usedChunkData < length)
  {
        int chunkID = 0;
        int chunkLength = 0;
        
        if(!loadChunkHeader(f, chunkID, chunkLength))
        {
            writeDebugText("C3DSFileHandler: Error reading an MATERIAL CHUNK\n");

            return false;
        }

        switch(chunkID)
        {
        
            case CHUNK_EDIT_MATERIAL_MATNAME:
            {
                writeDebugText("C3DSFileHandler: Found MATNAME CHUNK\n");

                // Texture name (filename without path)
                unsigned char textCh;
				int i = 0;
 
				while( ( textCh = fgetc(f) ) != 0 )
                {
                   matname[i] = textCh;
                   i++;
                }
                
                matname[i] = '\0';

                latestAddedTex = addTexture(matname);
                
                writeDebugText("C3DSFileHandler: Material Name = ");
                writeDebugText(matname);
                writeDebugText("\n");

            } break;
            
            case CHUNK_EDIT_MATERIAL_TEXTURE:
            {
                writeDebugText("C3DSFileHandler: Found TEXTURE CHUNK\n");

                ////////////////////////////////////////////////////////////////
                // The specification I read said it was possible, that some
                // of the CHUNK_EDIT_MATERIAL level data could be stored in the
                // CHUNK_EDIT_MATERIAL_TEXTURE level. So I recall the function
				// again.
                ////////////////////////////////////////////////////////////////
				if(!loadMaterialChunk(f, chunkLength - CHUNK_HEADER_SIZE))
                   return false;
                
            } break;
            
            case CHUNK_EDIT_MATERIAL_TEXFILENAME:
            {
                ////////////////////////////////////////////////////////////////
                // Texture name (filename without path)
                ////////////////////////////////////////////////////////////////
                unsigned char textCh;
                int i = 0;
 
                while( ( textCh = fgetc(f) ) != 0 )
                {
                   fname[i] = textCh;
                   i++;
                }
                
                fname[i] = '\0';
                 
                strcpy(texList.textures[latestAddedTex].fileName, fname);

                writeDebugText("C3DSFileHandler: Texture File = ");
				writeDebugText(texList.textures[latestAddedTex].fileName);
                writeDebugText(" added to Material ");
				writeDebugInt(latestAddedTex);
                writeDebugText("\n");

            } break;
            
            default:
            {
                writeDebugText("C3DSFileHandler: Found UNKNOWN CHUNK => will be skipped\n");
                writeDebugText("C3DSFileHandler: Unknown Chunk was ");
                writeDebugInt(chunkID);
                writeDebugText(" with a size of ");
                writeDebugInt(chunkLength);
                writeDebugText("\n");

                skipChunk(f, chunkLength);
                
            } break;

        }

        usedChunkData += chunkLength;
  }

  return (usedChunkData == length);
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::loadObjectChunk( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::loadObjectChunk(FILE *f, int length, const char *meshname)
{
  int usedChunkData = 0;
    
  writeDebugText("C3DSFileHandler: Begin to read an OBJECT CHUNK\n");

  while(usedChunkData < length)
  {
        int chunkID = 0;
        int chunkLength = 0;
        
        if(!loadChunkHeader(f, chunkID, chunkLength))
        {
            writeDebugText("C3DSFileHandler: Error reading an OBJECT CHUNK\n");

            return false;
        }

        switch(chunkID)
        {
            ///////////////////////////////////////////////////////////////////
            // This chunk contains information about a triangle mesh; 
            ///////////////////////////////////////////////////////////////////

            case CHUNK_EDIT_OBJECT_TRIMESH:
            {
                writeDebugText("C3DSFileHandler: Found TRIANGLE MESH CHUNK\n");

                if(!loadTriMeshChunk(f, chunkLength - CHUNK_HEADER_SIZE))
                {
                    writeDebugText("C3DSFileHandler: Could not read TRIANGLE MESH CHUNK\n");

                    return false;
                }

                ///////////////////////////////////////////////////////////////
                // Update the number of mesh objects found;
                ///////////////////////////////////////////////////////////////

                statsMeshCount++;

               
            } break;

            ///////////////////////////////////////////////////////////////////
            // If this chunk isn't needed, skip over it;
            ///////////////////////////////////////////////////////////////////

            default:
            {
                writeDebugText("C3DSFileHandler: Found UNKNOWN CHUNK => will be skipped\n");

                skipChunk(f, chunkLength);

            } break;
        }

        usedChunkData += chunkLength;
  }

  writeDebugText("C3DSFileHandler: OBJECT MESH read\n");

  return (usedChunkData == length);
}

///////////////////////////////////////////////////////////////////////////////
// C3DSFileHandler::loadTriMeshChunk( ... )
///////////////////////////////////////////////////////////////////////////////
// 
///////////////////////////////////////////////////////////////////////////////
bool C3DSFileHandler::loadTriMeshChunk(FILE *f, int length)
{
  int usedChunkData = 0;

  writeDebugText("C3DSFileHandler: Begin to read a TRIANGLE MESH CHUNK\n");

  ///////////////////////////////////////////////////////////////////////////
  // Create a new storage object for this triangle mesh;
  ///////////////////////////////////////////////////////////////////////////

  if( !pMeshStorage->addNewMesh() )
  {
     writeDebugText("C3DSFileHandler: Could not add new mesh\n");

     return false;
  }

  ///////////////////////////////////////////////////////////////////////////
  // Grab a pointer to the new mesh (added to the back);
  ///////////////////////////////////////////////////////////////////////////

  CMeshData *mesh = pMeshStorage->getMeshRear();
    
  if( mesh == NULL )
  {
     writeDebugText("C3DSFileHandler: Could not access new mesh\n");

     return false;
  }

  strcpy(mesh->m_name, "Imported 3DS Model");

  while(usedChunkData < length)
  {
        int chunkID = 0;
        int chunkLength = 0;
        
        if(!loadChunkHeader(f, chunkID, chunkLength))
        {
            return false;
        }

        switch(chunkID)
        {
            ///////////////////////////////////////////////////////////////////
            // This chunk contains the mesh's vertex coordinates;
            ///////////////////////////////////////////////////////////////////

            case CHUNK_EDIT_OBJECT_TRIMESH_VERTS:
            {
                unsigned short vertcount;

                writeDebugText("C3DSFileHandler: Begin to read a TRIANGLE MESH VERTEX CHUNK\n");

                fread(&vertcount, sizeof(unsigned short), 1, f);

                for(int i=0; i<vertcount; i++)
                {
                    TMeshVertex vert;

                    fread(&vert.x, sizeof(float), 1, f);
                    fread(&vert.y, sizeof(float), 1, f);
                    fread(&vert.z, sizeof(float), 1, f); 

                    mesh->addVert(vert);
                }

                statsTotalVerts += vertcount;

                writeDebugText("C3DSFileHandler: TRIANGLE MESH VERTEX CHUNK read\n");

            } break;
            
            ///////////////////////////////////////////////////////////////////
            // This chunk contains the mesh's face indices;
            ///////////////////////////////////////////////////////////////////

            case CHUNK_EDIT_OBJECT_TRIMESH_FACES:
            {
                unsigned short facecount = 0;

                writeDebugText("C3DSFileHandler: Begin to read a TRIANGLE MESH FACE CHUNK\n");

                fread(&facecount, sizeof(unsigned short), 1, f);

                for(int i=0; i<facecount; i++)
                {
                    TMeshTriangle face;

                    fread(&face.v[0], sizeof(unsigned short), 1, f);
                    fread(&face.v[1], sizeof(unsigned short), 1, f);
                    fread(&face.v[2], sizeof(unsigned short), 1, f);

                    unsigned short useless;

                    fread(&useless, sizeof(unsigned short), 1, f);

                    mesh->addFace(face);
                }

                writeDebugText("C3DSFileHandler: TRIANGLE MESH FACE CHUNK read\n");

                statsTotalFaces += facecount;

                ///////////////////////////////////////////////////////////////
                // Nodes deeper in the hierarchy than this face node are of no
                // interest to this loader. However, this chunk may contain
                // children.  This loop checks for the existence of any child
                // nodes and skips them if found.
                ///////////////////////////////////////////////////////////////

                for(int j=0; j<2; j++)
                {
                    int eChunkID = 0;
                    int eChunkLength = 0;

                    if(!loadChunkHeader(f, eChunkID, eChunkLength))
                    {
                        return false;
                    }

                    ///////////////////////////////////////////////////////////
                    // Check if the loaded chunk is a child of the face chunk;
                    ///////////////////////////////////////////////////////////

                    switch (eChunkID)
                    {
                    
                        case CHUNK_EDIT_OBJECT_TRIMESH_MATERIAL:
                        {
                            writeDebugText("C3DSFileHandler: Found TRIMESH_MATERIAL CHUNK\n");

                            ////////////////////////////////////////////////////////////////
                            // Material name 
                            ////////////////////////////////////////////////////////////////
                            unsigned char textCh;
                            char matName[512];
                            int i = 0;
                            
                            while( ( textCh = fgetc(f) ) != 0 )
                            {
                                matName[i] = textCh;
                                i++;
                            }
                
                            matName[i] = '\0';
                 
                            writeDebugText("C3DSFileHandler: Material File = ");
                            writeDebugText(matName);
                            writeDebugText("\n");
                            
                            int n = 0;
                            
                            fread(&n, 2, 1, f);
                            
                            int faceNr = 0;
                            
                            writeDebugText("C3DSFileHandler: Material FaceNr = ");
                            writeDebugInt(n);
                            writeDebugText("\n");
                            
                            ////////////////////////////////////////////////////
                            // This faceNr contain the values you have to 
                            // substract from the actual nr of faces
                            ////////////////////////////////////////////////////

                            writeDebugText("C3DSFileHandler: Face Corresponding Texture Nr = ");
                            while ( n-- > 0 )
                            {
                                fread(&faceNr, 2, 1, f);
                                mesh->getFace(mesh->getFaceCount()-1-faceNr)->texture =
                                              getTextureNr(matName);
                                              
                                writeDebugText(" [");
                                writeDebugInt(mesh->getFaceCount()-1-faceNr);
                                writeDebugText("] ");

                                writeDebugInt(mesh->getFace(mesh->getFaceCount()-1-faceNr)->texture);

                                writeDebugText(" ");
                            }

                            writeDebugText("\n");

                        }; break;
                        
                        case CHUNK_EDIT_OBJECT_TRIMESH_SMOOTH:
                        {
                            writeDebugText("C3DSFileHandler: Found TRIMESH_SMOOTH CHUNK => will be skipped\n");

                            skipChunk(f, eChunkLength);
                        }; break;
                        
                        default :
                        {
                            fseek(f, -CHUNK_HEADER_SIZE, SEEK_CUR);
                        }; break;

                    } 

                }

            } break;

            ///////////////////////////////////////////////////////////////////
            // This chunk contains the mesh's (vertex) UV coordinates;
            ///////////////////////////////////////////////////////////////////

            case CHUNK_EDIT_OBJECT_TRIMESH_TEXUV:
            {
                unsigned short vertcount;

                writeDebugText("C3DSFileHandler: Begin to read a TRIANGLE MESH UV CHUNK\n");

                fread(&vertcount, sizeof(unsigned short), 1, f);

                if(vertcount != mesh->getVertCount())
                {
                    return false;
                }

                writeDebugText("C3DSFileHandler: u-v TexCoords: ");

                for (int i=0; i<vertcount; i++)
                {
                    TMeshVertex *ptrVert = mesh->getVert(i);

                    if(ptrVert == NULL)
                    {
                        return false;
                    }

                    fread(&ptrVert->u, sizeof(float), 1, f);
                    fread(&ptrVert->v, sizeof(float), 1, f);
                    
                    writeDebugFloat(ptrVert->u);
                    writeDebugText("-");
                    writeDebugFloat(ptrVert->v);
                    writeDebugText(" ");
                }  
                
                writeDebugText("\n");

                writeDebugText("C3DSFileHandler: Vertexes containing UV Tex Coords:");
                writeDebugInt(vertcount);
                writeDebugText("\n");
                            
                writeDebugText("C3DSFileHandler: TRIANGLE MESH UV CHUNK read\n");

            } break;

            ///////////////////////////////////////////////////////////////////
            // If this chunk isn't needed, skip over it;
            ///////////////////////////////////////////////////////////////////

            default:
            {
                writeDebugText("C3DSFileHandler: Found UNKNOWN CHUNK => will be skipped\n");

                skipChunk(f, chunkLength);

            } break;
        }

        usedChunkData += chunkLength;
  } 

  writeDebugText("C3DSFileHandler: TRIANGLE MESH read\n");

  return (usedChunkData == length);
}
